#pragma once

#include <cassert>
#include <memory>
#include <stack>
#include <vector>
#include "IR.h"


namespace sysy
{
    // 获取数组声明中数组各维度大小及总大小
    inline std::tuple<std::vector<int>, int> getRealDims(const std::vector<Value*>& dims)
    {
        int size = 1;
        std::vector<int> rdims;
        for (auto v : dims)
        {
            auto i = dynamic_cast<ConstantValue*>(v)->getInt();
            size *= i;
            rdims.push_back(i);
        }
        return {rdims, size};
    }

    class IRBuilder
    {
    private:
        // 当前所在位置的基本块指针
        BasicBlock* block;
        BasicBlock::iterator position;

        // 各个语法结构的数量, 用于给标签命名等
        int ifNum, whileNum, funcNum;

        // 跳转的目标块
        std::stack<BasicBlock*> trueTargets, falseTargets;

    public:
        // 数组初始值定义时各个维度的大小

        struct
        {
            std::vector<int> realDims;
            std::vector<int> currentOffsets;
            std::vector<int> maxSize;
            int pos, maxSizeLowerBound;
            AllocaInst* allocaValue;
            GlobalValue* globalValue;
            bool isAlloca, onArray;
        } arrayInfo;

        // lval这个语法元素既有左值又有右值
        // 第一遍处理的时候没有明确两者界限
        // 现在通过这个bool变量明确当前语义
        // 默认false, 即右值; 在需要左值(visitStmtAssign)时, 置为true; 处理后, 由该函数负责置为false.
        bool isPureLval;

        // 需要划分新基本块, 比如一个block下多个ret
        bool unreachableBelow;

    public:
        int getIfNum() const { return ifNum; }

        int getWhileNum() const { return whileNum; }

        int getFuncNum() const { return funcNum; }

    public:
        IRBuilder() :
            block(nullptr), position(), ifNum(0), whileNum(0), funcNum(0), trueTargets(), falseTargets(),
            isPureLval(false), arrayInfo({{}, {}, {}, 0, 0, nullptr, nullptr, false, false}),
            unreachableBelow(false)
        {
        }

        IRBuilder(BasicBlock* block) : block(block), position(block->end())
        {
        }

        IRBuilder(BasicBlock* block, BasicBlock::iterator position) : block(block), position(position)
        {
        }

    public:
        BasicBlock* getBasicBlock() const { return block; }

        BasicBlock::iterator getPosition() const { return position; }

        void setPosition(BasicBlock* block, BasicBlock::iterator position)
        {
            this->block = block;
            this->position = position;
        }

        void setPosition(BasicBlock::iterator position) { this->position = position; }

        void addIfNum() { ifNum++; }

        void addWhileNum() { whileNum++; }

        void addFuncNum() { funcNum++; }

        void pushTargets(BasicBlock* true_target, BasicBlock* false_target)
        {
            trueTargets.push(true_target);
            falseTargets.push(false_target);
        }

        void popTargets()
        {
            trueTargets.pop();
            falseTargets.pop();
        }

        std::tuple<BasicBlock*, BasicBlock*> getTargets() { return {trueTargets.top(), falseTargets.top()}; }

    public:
        UnaryInst* createUnaryInst(Instruction::Kind kind, Type* type, Value* operand, const std::string& name = "")
        {
            auto inst = new UnaryInst(kind, type, operand, block, name);
            assert(inst);
            block->getInstructions().emplace(position, inst);
            return inst;
        }

        UnaryInst* createNegInst(Value* operand, const std::string& name = "")
        {
            return createUnaryInst(Instruction::kNeg, Type::getIntType(32), operand, name);
        }

        UnaryInst* createNotInst(Value* operand, const std::string& name = "")
        {
            return createUnaryInst(Instruction::kNot, Type::getIntType(1), operand, name);
        }

        UnaryInst* createFtoIInst(Value* operand, const std::string& name = "")
        {
            return createUnaryInst(Instruction::kFtoI, Type::getIntType(32), operand, name);
        }

        UnaryInst* createFNegInst(Value* operand, const std::string& name = "")
        {
            return createUnaryInst(Instruction::kFNeg, Type::getFloatType(), operand, name);
        }

        UnaryInst* createIToFInst(Value* operand, const std::string& name = "")
        {
            return createUnaryInst(Instruction::kIToF, Type::getFloatType(), operand, name);
        }

        UnaryInst* createI1ToI32Inst(Value* operand, const std::string& name = "")
        {
            return createUnaryInst(Instruction::kZext, Type::getIntType(32), operand, name);
        }

        BinaryInst* createBinaryInst(Instruction::Kind kind, Type* type, Value* lhs, Value* rhs,
                                     const std::string& name = "")
        {
            auto inst = new BinaryInst(kind, type, lhs, rhs, block, name);
            assert(inst);
            block->getInstructions().emplace(position, inst);
            return inst;
        }

        BinaryInst* createAddInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kAdd, Type::getIntType(32), lhs, rhs, name);
        }

        BinaryInst* createSubInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kSub, Type::getIntType(32), lhs, rhs, name);
        }

        BinaryInst* createMulInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kMul, Type::getIntType(32), lhs, rhs, name);
        }

        BinaryInst* createDivInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kDiv, Type::getIntType(32), lhs, rhs, name);
        }

        BinaryInst* createRemInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kRem, Type::getIntType(32), lhs, rhs, name);
        }

        BinaryInst* createICmpEQInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kICmpEQ, Type::getIntType(32), lhs, rhs, name);
        }

        BinaryInst* createICmpNEInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kICmpNE, Type::getIntType(32), lhs, rhs, name);
        }

        BinaryInst* createICmpLTInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kICmpLT, Type::getIntType(32), lhs, rhs, name);
        }

        BinaryInst* createICmpLEInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kICmpLE, Type::getIntType(32), lhs, rhs, name);
        }

        BinaryInst* createICmpGTInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kICmpGT, Type::getIntType(32), lhs, rhs, name);
        }

        BinaryInst* createICmpGEInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kICmpGE, Type::getIntType(32), lhs, rhs, name);
        }

        BinaryInst* createFAddInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kFAdd, Type::getFloatType(), lhs, rhs, name);
        }

        BinaryInst* createFSubInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kFSub, Type::getFloatType(), lhs, rhs, name);
        }

        BinaryInst* createFMulInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kFMul, Type::getFloatType(), lhs, rhs, name);
        }

        BinaryInst* createFDivInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kFDiv, Type::getFloatType(), lhs, rhs, name);
        }

        BinaryInst* createFRemInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kFRem, Type::getFloatType(), lhs, rhs, name);
        }

        BinaryInst* createFCmpEQInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kFCmpEQ, Type::getFloatType(), lhs, rhs, name);
        }

        BinaryInst* createFCmpNEInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kFCmpNE, Type::getFloatType(), lhs, rhs, name);
        }

        BinaryInst* createFCmpLTInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kFCmpLT, Type::getFloatType(), lhs, rhs, name);
        }

        BinaryInst* createFCmpLEInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kFCmpLE, Type::getFloatType(), lhs, rhs, name);
        }

        BinaryInst* createFCmpGTInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kFCmpGT, Type::getFloatType(), lhs, rhs, name);
        }

        BinaryInst* createFCmpGEInst(Value* lhs, Value* rhs, const std::string& name = "")
        {
            return createBinaryInst(Instruction::kFCmpGE, Type::getFloatType(), lhs, rhs, name);
        }

        GEPInst* createGEPInst(Value* pointer, std::vector<Value*>& indices, const std::string& name)
        {
            auto inst = new GEPInst(pointer, indices, block, name);
            assert(inst);
            block->getInstructions().emplace(position, inst);
            return inst;
        }

        ReturnInst* createReturnInst(Value* value = nullptr, const std::string& name = "")
        {
            auto inst = new ReturnInst(value);
            assert(inst);
            block->getInstructions().emplace(position, inst);
            unreachableBelow = true;
            return inst;
        }

        UncondBrInst* createUncondBrInst(BasicBlock* block)
        {
            if (!this->block->instructions.empty() && this->block->instructions.rbegin()->get()->isTerminator())
                return nullptr;

            auto inst = new UncondBrInst(block, block);
            assert(inst);
            this->block->getInstructions().emplace(position, inst);
            unreachableBelow = true;
            return inst;
        }

        CondBrInst* createCondBrInst(Value* condition, BasicBlock* thenBlock, BasicBlock* elseBlock)
        {
            auto inst = new CondBrInst(condition, thenBlock, elseBlock, block);
            assert(inst);
            block->getInstructions().emplace(position, inst);
            unreachableBelow = true;
            return inst;
        }

        AllocaInst* createAllocaInst(Type* type, const std::vector<Value*>& dims = {}, const std::string& name = "",
                                     bool isConst = false, int arraySize = 0)
        {
            auto inst = new AllocaInst(type, dims, block, name, isConst, arraySize);
            assert(inst);
            block->getInstructions().emplace(position, inst);
            return inst;
        }

        LoadInst* createLoadInst(Value* pointer, const std::string& name = "")
        {
            auto inst = new LoadInst(pointer, block, name);
            assert(inst);
            block->getInstructions().emplace(position, inst);
            return inst;
        }

        StoreInst* createStoreInst(Value* value, Value* pointer, const std::string& name = "")
        {
            auto inst = new StoreInst(value, pointer, block, name);
            assert(inst);
            block->getInstructions().emplace(position, inst);
            return inst;
        }

        CallInst* createCallInst(Function* callee, const std::vector<Value*>& args = {}, const std::string& name = "")
        {
            auto inst = new CallInst(callee, args, block, name);
            assert(inst);
            block->getInstructions().emplace(position, inst);
            return inst;
        }
    };
} // namespace sysy
